package dao

import (
	"context"
	"time"

	"github.com/ego-component/egorm"
	"github.com/go-sql-driver/mysql"
	"github.com/pkg/errors"
	"gorm.io/gorm"
)

type ChannelDAO interface {
	Insert(ctx context.Context, c Channel) (Channel, error)
	FindByID(ctx context.Context, id int64) (Channel, error)
	FindByIDs(ctx context.Context, ids []int64) ([]Channel, error)
	DeleteByID(ctx context.Context, id int64) error
	UpdateInfoByID(ctx context.Context, c Channel) (Channel, error)
	FindAll(ctx context.Context) ([]Channel, error)
	InsertOneToOne(ctx context.Context, c Channel, member, targetMember ChannelMember) (Channel, ChannelMember, error)
	FindOneToOne(ctx context.Context, uid, targetUID int64) (Channel, error)
}

type gormChannelDAO struct {
	db *egorm.Component
}

func NewGORMChannelDAO(db *egorm.Component) ChannelDAO {
	return &gormChannelDAO{
		db: db,
	}
}

func (dao *gormChannelDAO) Insert(ctx context.Context, c Channel) (Channel, error) {
	now := time.Now().UnixMilli()
	c.Ctime = now
	err := dao.db.WithContext(ctx).Create(&c).Error
	return c, err
}

func (dao *gormChannelDAO) FindByID(ctx context.Context, id int64) (Channel, error) {
	var c Channel
	err := dao.db.WithContext(ctx).First(&c, id).Error
	return c, err
}

func (dao *gormChannelDAO) DeleteByID(ctx context.Context, id int64) error {
	cm := Channel{ID: id}
	return dao.db.WithContext(ctx).Delete(&cm).Error
}

func (dao *gormChannelDAO) UpdateInfoByID(ctx context.Context, c Channel) (Channel, error) {
	now := time.Now().UnixMilli()
	res := dao.db.WithContext(ctx).Model(&c).
		Updates(map[string]any{
			"name":  c.Name,
			"utime": now,
		})
	if res.RowsAffected == 0 {
		return Channel{}, ErrChannelRecordNotFound
	}
	return c, res.Error
}

func (dao *gormChannelDAO) FindByIDs(ctx context.Context, ids []int64) ([]Channel, error) {
	var res []Channel
	err := dao.db.WithContext(ctx).Where("id IN (?)", ids).Find(&res).Error
	return res, err
}

func (dao *gormChannelDAO) FindAll(ctx context.Context) ([]Channel, error) {
	var res []Channel
	err := dao.db.WithContext(ctx).Find(&res).Error
	return res, err
}

func (dao *gormChannelDAO) InsertOneToOne(ctx context.Context, c Channel, member, targetMember ChannelMember) (Channel, ChannelMember, error) {
	uid1, uid2 := min(member.UID, targetMember.UID), max(member.UID, targetMember.UID)
	now := time.Now().UnixMilli()
	err := dao.db.Transaction(func(tx *gorm.DB) error {
		c.Ctime = now
		err := tx.WithContext(ctx).Create(&c).Error
		if err != nil {
			return err
		}
		err = tx.WithContext(ctx).Create(&OneToOneExtend{
			CID:   c.ID,
			UID1:  uid1,
			UID2:  uid2,
			Ctime: now,
		}).Error
		if err != nil {
			return err
		}
		member.ChannelID, targetMember.ChannelID = c.ID, c.ID
		member.Ctime, targetMember.Ctime = now, now
		err = tx.WithContext(ctx).Create(&member).Error
		if err != nil {
			return err
		}
		return tx.WithContext(ctx).Create(&targetMember).Error
	})
	me := new(mysql.MySQLError)
	if ok := errors.As(err, &me); ok {
		const uniqueConflicts uint16 = 1062
		if me.Number == uniqueConflicts {
			return Channel{}, ChannelMember{}, ErrDuplicateRecord
		}
	}
	return c, member, err
}

func (dao *gormChannelDAO) FindOneToOne(ctx context.Context, uid, targetUID int64) (Channel, error) {
	uid1, uid2 := min(uid, targetUID), max(uid, targetUID)
	var exd OneToOneExtend
	err := dao.db.WithContext(ctx).
		Where("uid1 = ? AND uid2 = ?", uid1, uid2).
		First(&exd).Error
	if err != nil {
		return Channel{}, err
	}
	var c Channel
	err = dao.db.WithContext(ctx).First(&c, exd.CID).Error
	return c, err
}

type Channel struct {
	ID    int64  `gorm:"primaryKey,autoIncrement"`
	Name  string `gorm:"type:varchar(256);default:'';comment:群组名称"`
	Type  string `gorm:"type:varchar(64);default:'';comment:群组类型"`
	Ctime int64  `gorm:"not null;default:0;comment:创建时间"`
	Utime int64  `gorm:"not null;default:0;comment:更新时间UTC毫秒数"`
}

type OneToOneExtend struct {
	ID  int64 `gorm:"primaryKey,autoIncrement"`
	CID int64 `gorm:"unique"`
	// 注意，UID1 和 UID2 必须要按照大小排序，UID1 较小而 UID2 较大
	UID1  int64 `gorm:"uniqueIndex:uid1_uid2"`
	UID2  int64 `gorm:"uniqueIndex:uid1_uid2"`
	Ctime int64
	Utime int64
}
